﻿namespace CalculatorLibrary.Parser
{
    using System.Collections.Generic;
    using System.Linq;
    using System.Xml;
    using System.Xml.Linq;

    /// <summary>
    /// Class XmlParser
    /// </summary>
    public class XmlParser
    {
        private CalculationResult _errorResult;

        private Dictionary<string, string> _expressions;

        #region Public Methods and Operators

        /// <summary>
        /// Parses the input file.
        /// </summary>
        /// <param name="file">The file.</param>
        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
        public bool ParseInputFile(XDocument file)
        {
            _expressions = new Dictionary<string, string>();
            _errorResult = new CalculationResult
                               {
                                   IsError = true,
                                   ReturnState = ReturnState.Terminate,
                               };
            List<XElement> baseAttributes = file.Descendants(XmlElementConstant.DeploymentNode).ToList();
            if (baseAttributes.Count != 1)
            {
                return new OperationResult(
                    ErrorType.ParseError,
                    string.Format("Can't find root '{0}' node.", XmlElementConstant.DeploymentNode));
            }

            XElement deployment = baseAttributes.First();
            result.SolutionPath = GetValue(deployment, XmlElementConstant.DeploymentSolutionPathAttribute);
            result.LogsPath = GetValue(deployment, XmlElementConstant.DeploymentLogsPathAttribute);
            List<XElement> targetElements = deployment.Descendants(XmlElementConstant.TargetNode).ToList();
            foreach (XElement targetElement in targetElements)
            {
                var targetSystem = new TargetSystem
                {
                    BuildConfiguration =
                        GetValue(
                            targetElement,
                            XmlElementConstant.TargetBuildConfigurationAttribute),
                    TargetSystemName =
                        GetValue(
                            targetElement,
                            XmlElementConstant.TargetNameAttribute),
                    DeployableProjects = new List<DeployableProject>()
                };
                if (string.IsNullOrEmpty(targetSystem.TargetSystemName)
                    || string.IsNullOrEmpty(targetSystem.BuildConfiguration))
                {
                    var message =
                        string.Format(
                            "Required attributes '{1}' or '{2}' for a target are not specified at line {0}",
                            GetLineNumberForElement(targetElement),
                            XmlElementConstant.TargetBuildConfigurationAttribute,
                            XmlElementConstant.TargetNameAttribute);
                    return new OperationResult(ErrorType.ParseError, message);
                }

                bool targetAlreadyExists =
                    result.TargetSystems.FirstOrDefault(t => t.TargetSystemName == targetSystem.TargetSystemName)
                    != null;
                if (targetAlreadyExists)
                {
                    var message = string.Format(
                        "Duplicate target system with name {0} was found at line {1}",
                        targetSystem.TargetSystemName,
                        GetLineNumberForElement(targetElement));
                    return new OperationResult(ErrorType.ParseError, message);
                }

                List<XElement> deployableProjectElements =
                    targetElement.Descendants(XmlElementConstant.ProjectNode).ToList();
                foreach (XElement deployableProjectElement in deployableProjectElements)
                {
                    var deployableProject = new DeployableProject
                    {
                        ProjectName =
                            GetValue(
                                deployableProjectElement,
                                XmlElementConstant.ProjectNameAttribute),
                        ProjectPath =
                            GetValue(
                                deployableProjectElement,
                                XmlElementConstant.ProjectPathAttribute),
                        PublishProfile =
                            GetValue(
                                deployableProjectElement,
                                XmlElementConstant.ProjectPublishProfileAttribute),
                        UserName =
                            GetValue(
                                deployableProjectElement,
                                XmlElementConstant.ProjectUsernameAttribute),
                        Password =
                            GetValue(
                                deployableProjectElement,
                                XmlElementConstant.ProjectPasswordAttribute),
                        TargetSystemName = targetSystem.TargetSystemName
                    };
                    if (string.IsNullOrEmpty(deployableProject.ProjectName))
                    {
                        var message =
                            string.Format(
                                "Required attribute '{1}' for a project is not specified at line {0}",
                                GetLineNumberForElement(deployableProjectElement),
                                XmlElementConstant.ProjectNameAttribute);
                        return new OperationResult(
                            ErrorType.ParseError, message);
                    }

                    bool alreadyExist =
                        targetSystem.DeployableProjects.FirstOrDefault(
                            p => p.ProjectName == deployableProject.ProjectName) != null;
                    if (alreadyExist)
                    {
                        var message = string.Format(
                            "Duplicate project with name {0} was found at line {1}",
                            deployableProject.ProjectName,
                            GetLineNumberForElement(deployableProjectElement));
                        return new OperationResult(
                            ErrorType.ParseError, message);
                    }

                    targetSystem.DeployableProjects.Add(deployableProject);
                }

                result.TargetSystems.Add(targetSystem);
            }

            return new OperationResult(result);
        }

        #endregion

        #region Methods

        /// <summary>
        /// The get line number for element.
        /// </summary>
        /// <param name="element">
        /// The element.
        /// </param>
        /// <returns>
        /// The <see cref="int"/>.
        /// </returns>
        private static int GetLineNumberForElement(XElement element)
        {
            return ((IXmlLineInfo)element).HasLineInfo() ? ((IXmlLineInfo)element).LineNumber : -1;
        }

        /// <summary>
        /// The get value.
        /// </summary>
        /// <param name="element">
        /// The element.
        /// </param>
        /// <param name="attributeName">
        /// The attribute name.
        /// </param>
        /// <returns>
        /// The <see cref="string"/>.
        /// </returns>
        private static string GetValue(XElement element, string attributeName)
        {
            XAttribute attribute = element.Attribute(attributeName);
            return attribute != null ? attribute.Value : string.Empty;
        }

        #endregion 
    }
}